<template>
  <div id="container">
    <div style="height: 55px;background-color: #444444;">上面布局</div>
    <!-- 下 -->
    <div>
      <div id="left" style="position: absolute;top: 60px;background-color: #444444;width: 195px;">下面左侧布局</div>
      <div id="webgl" style="position: absolute;top: 60px;left: 200px;background-color: #444444;">
        <div id="red" style="color: #ff0000;z-index:20;position: absolute;left:0;">红色</div>
        <div id="green" style="color: #ff0000;z-index:20;position: absolute;right:20px;">绿色</div>

        <button type="button" name="button" @click="saveFile"
          style="color: #ff0000;z-index:20;position: absolute;right:40px;">下载</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import * as THREE from 'three'
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { onMounted } from 'vue';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
// #
// 渲染器通道RenderPass

// RenderPass.js扩展库目录：examples / jsm / postprocessing /

// 引入渲染器通道RenderPass
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';


// OutlinePass通道

// OutlinePass可以给指定的某个模型对象添加一个高亮发光描边效果。

// OutlinePass.js扩展库目录：examples / jsm / postprocessing /

// 引入OutlinePass通道
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
// Bloom发光通道UnrealBloomPass

// UnrealBloomPass.js扩展库目录：examples / jsm / postprocessing /

// 引入UnrealBloomPass通道
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';


// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

const width = window.innerWidth - 200;
const height = window.innerHeight - 60;

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
  //想把canvas画布上内容下载到本地，需要设置为true
  preserveDrawingBuffer: true,
  // 设置对数深度缓冲区，优化深度冲突问题
  // // 两个矩形平面Mesh重合，产生闪烁
  // 闪烁原因：两个矩形面位置重合，GPU无法分清谁在前谁在后
  // 在三维软件中建模的的时候，尽量避免两个Mesh完全重合，可以考虑适当偏移一定的距离
  // 当一个三维场景中有一些面距离比较近，有深度冲突，你可以尝试设置webgl渲染器设置对数深度缓冲区logarithmicDepthBuffer: true来优化或解决。logarithmicDepthBuffer: true作用简单来说，就是两个面间距比较小的时候，让threejs更容易区分两个面，谁在前，谁在后。

  logarithmicDepthBuffer: true
});
// renderer.setSize(window.innerWidth, window.innerHeight);


// document.body.appendChild(renderer.domElement);
//创建一个长方体几何对象Geometry
// //BoxGeometry：长方体
// const geometry = new THREE.BoxGeometry(50, 50, 50);
//创建一个空的几何体对象
const geometry = new THREE.BoxGeometry(30, 30, 30);
// canvas画布绝对定位
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0px';
renderer.domElement.style.left = '0px';
renderer.domElement.style.zIndex = 10;
// 立方体纹理加载器CubeTextureLoader
// TextureLoader返回Texture
// CubeTextureLoader返回CubeTexture

// 通过前面学习大家知道，通过纹理贴图加载器TextureLoader的.load()方法加载一张图片可以返回一个纹理对象Texture。

// 立方体纹理加载器CubeTextureLoader的.load()方法是加载6张图片，返回一个立方体纹理对象CubeTexture。

// 立方体纹理对象CubeTexture的父类是纹理对象Texture。
// #
// CubeTextureLoader加载环境贴图

// CubeTextureLoader加载环境贴图

// 所谓环境贴图，就是一个模型周围的环境的图像，比如一间房子，房子的上下左右前后分别拍摄一张照片，就是3D空间中6个角度方向的照片。


const material = new THREE.MeshStandardMaterial({
  color: 0x0000ff, //材质颜色
});

const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
// 在XOZ平面上分布
mesh.position.set(0, 0, 0);
const scene = new THREE.Scene()
scene.add(mesh)


// 两个矩形平面Mesh重合，产生闪烁
// 闪烁原因：两个矩形面位置重合，GPU无法分清谁在前谁在后
const geometry1 = new THREE.PlaneGeometry(150, 150);
const material1 = new THREE.MeshLambertMaterial({
  color: 0x00ffff,
  side: THREE.DoubleSide,
});
const mesh1 = new THREE.Mesh(geometry1, material1);
mesh1.position.set(0, 0, 100)

const geometry2 = new THREE.PlaneGeometry(100, 100);
const material2 = new THREE.MeshLambertMaterial({
  color: 0xff6666,
  side: THREE.DoubleSide,
});
const mesh2 = new THREE.Mesh(geometry2, material2);
mesh2.position.set(0, 0, 101)
scene.add(mesh1)
scene.add(mesh2)



// 添加一个辅助网格地面
const gridHelper = new THREE.GridHelper(10, 10, 0x004444, 0x004444);
scene.add(gridHelper)

// AxesHelper：辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(20);
scene.add(axesHelper);
// 实例化一个透视投影相机对象
// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000：远裁截面
// // 超出视锥体远裁界面的范围的会被剪裁掉，不渲染  可以调整far参数适配 所以要看全部就把远裁面拉高10000
// const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
// 200表示左侧div元素宽度195px+间距5px
const width2 = window.innerWidth - 200; //canvas画布高度
//60表示顶部div元素高度55px+间距5px
const height2 = window.innerHeight - 60; //canvas画布宽度
const camera = new THREE.PerspectiveCamera(30, width2 / height2, 1, 3000);
renderer.setSize(width2, height2);
//相机在Three.js三维坐标系中的位置
// 根据需要设置相机位置具体值
camera.position.set(100, 100, 100);
// camera.lookAt(mesh.position);//指向mesh对应的位置
// // 改变相机观察目标点
camera.lookAt(10, 0, 0);


//点光源：两个参数分别表示光源颜色和光照强度
// 参数1：0xffffff是纯白光,表示光源颜色
// 参数2：1.0,表示光照强度，可以根据需要调整
const pointLight = new THREE.PointLight(0xffffff, 1.0);
pointLight.decay = 0.0;//设置光源不随距离衰减
//点光源位置
pointLight.position.set(200, 200, 300);//点光源放在x轴上
scene.add(pointLight); //点光源添加到场景中

//环境光:没有特定方向，整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(80, 100, 50);
// 方向光指向对象网格模型mesh，可以不设置，默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);
// DirectionalLightHelper：可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
scene.add(dirLightHelper);

// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log('查看当前屏幕设备像素比', window.devicePixelRatio);
// 或者直接设置参数  renderer.antialias = true

// 如果你遇到你的canvas画布输出模糊问题，注意设置renderer.setPixelRatio(window.devicePixelRatio)。
// 注意：注意你的硬件设备设备像素比window.devicePixelRatio刚好是1，那么是否执行.setPixelRatio()不会有明显差异，不过为了适应不同的硬件设备屏幕，通常需要执行该方法。
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);

renderer.setClearColor(0x444444, 1); //设置背景颜色

// 大家都知道three.js WebGL渲染器执行渲染方法.render()会得到一张图像，如果你需要对一个webgl渲染器的渲染结果进行后期处理，就把它作为EffectComposer的参数。

// 创建后处理对象EffectComposer，WebGL渲染器作为参数
const composer = new EffectComposer(renderer);
// 通过EffectComposer(renderer)指定了需要后处理的渲染器WebGLRenderer，渲染器通道RenderPass的作用是指定后处理对应的相机camera和场景scene。

// 创建一个渲染器通道，场景和相机作为参数
const renderPass = new RenderPass(scene, camera);

// 给EffectComposer添加一个渲染器通道RenderPass。

// 设置renderPass通道
composer.addPass(renderPass);
// 创建OutlinePass通道

// OutlinePass第一个参数v2的尺寸和canvas画布保持一致
const v2 = new THREE.Vector2(width2, height2);
// const v2 = new THREE.Vector2(800, 600);
const outlinePass = new OutlinePass(v2, scene, camera);
// OutlinePass属性.selectedObjects

// three.js场景中有多个模型的话，你希望给哪个模型对象设置发光描边效果，就可以通过OutlinePass的选择对象属性.selectedObjects设置。
//模型描边颜色，默认白色         
outlinePass.visibleEdgeColor.set(0xffff00);
//高亮发光描边厚度
outlinePass.edgeThickness = 4;
//高亮描边发光强度
outlinePass.edgeStrength = 6;
//模型闪烁频率控制，默认0不闪烁
outlinePass.pulsePeriod = 2;

// 一个模型对象
outlinePass.selectedObjects = [mesh, mesh1, mesh2];
// 多个模型对象
// outlinePass.selectedObjects = [mesh1, mesh2, group];


// UnrealBloomPass参数1是一个二维向量Vector2，二维向量尺x、y分量要和Canvas画布的宽、高度尺寸保持一致。

// canvas画布宽高度尺寸是800, 600
const bloomPass = new UnrealBloomPass(new THREE.Vector2(width2, height2))
//  发光强度.strength

// Bloom发光强度bloomPass.strength，默认1.0。

// console.log('发光强度',bloomPass.strength);

//bloom发光强度
bloomPass.strength = 2.0;


// 设置OutlinePass通道

// 最后把创建好的OutlinePass通道添加到后处理composer中。

// 设置OutlinePass通道
composer.addPass(outlinePass);


function render() {
  composer.render()
  // console.log('camera', camera.position)
  // renderer.render(scene, camera); //执行渲染操作
  requestAnimationFrame(render);//请求再次执行渲染函数render，渲染下一帧
}
render();

const controls = new OrbitControls(camera, renderer.domElement);
// 相机控件.target属性在OrbitControls.js内部表示相机目标观察点，默认0,0,0
// console.log('controls.target', controls.target);
controls.target.set(10, 0, 0);
controls.update();//update()函数内会执行camera.lookAt(controls.targe)


// 如果OrbitControls改变了相机参数，重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
  // 浏览器控制台查看相机位置变化
  // console.log('camera.position', camera.position);
  // 渲染循环和相机控件OrbitControls

  // 设置了渲染循环, 相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera);，毕竟渲染循环一直在执行renderer.render(scene, camera);。
  // renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件
// onresize 事件会在窗口被调整大小时发生
// window.onresize = function () {
//   // 重置渲染器输出画布canvas尺寸
//   renderer.setSize(window.innerWidth, window.innerHeight);
//   // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
//   camera.aspect = window.innerWidth / window.innerHeight;
//   // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
//   // 但是不会每渲染一帧，就通过相机的属性计算投影矩阵(节约计算资源)
//   // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
//   camera.updateProjectionMatrix();
// };
// 画布跟随窗口变化
window.onresize = function () {
  const width = window.innerWidth - 200; //canvas画布高度
  const height = window.innerHeight - 60; //canvas画布宽度
  renderer.setSize(width, height);
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
};
onMounted(() => {
  document.getElementById('left').style.height = height + 'px';
  document.getElementById('webgl').style.width = width + 'px';
  document.getElementById('webgl').style.height = height + 'px';

  document.getElementById('webgl').appendChild(renderer.domElement)

  document.getElementById('red').addEventListener('click', function () {
    mesh.material.color.set(0xff0000);
  })
  document.getElementById('green').addEventListener('click', function () {
    mesh.material.color.set(0x00ff00);
  })

})

const saveFile = () => {
  // 创建一个超链接元素，用来下载保存数据的文件
  const link = document.createElement('a');
  // 通过超链接herf属性，设置要保存到文件中的数据

  // Canvas画布通过.toDataURL()方法可以获取画布上的像素信息。canvas.toDataURL("image/png");表示以png格式获取像素数据，可以直接赋值给超链接元素a的.herf属性下载到本地。

  const canvas = renderer.domElement; //获取canvas对象
  link.href = canvas.toDataURL("image/png");
  // link.href = window.URL.createObjectURL(new Blob(['一些数据']));

  link.download = 'threejs.png';//下载文件名
  link.click();//js代码触发超链接元素a的鼠标点击事件，开始下载文件到本地
}
</script>
<style scoped>
#container {
  width: 100%;
  height: 100%;
}

#canvas {
  width: 100%;
  height: 100%;
  position: relative;
  top: 0;
  left: 0;
}
</style>